import graph.core.IEdge;
import graph.core.IGraph;
import graph.core.IIterator;
import graph.core.IVertex;
import graph.impl.AdjacencyListGraph;

/**
 * Test class for the Adjacency List implementation of the Graph data structure.
 * 
 * This test class is used to verify the correctness of the adjacency list graph
 * implementation. Tests include:
 * 1. Basic operations testing (inserting/removing vertices and edges)
 * 2. Query operations testing (getting endpoints, checking adjacency
 * relationships, etc.)
 * 3. Replacement operations testing (replacing vertex and edge elements)
 * 4. Operation side-effect testing (e.g., removing a vertex removes its
 * associated edges)
 * 
 * Tests use assertion-style verification, automatically detecting issues and
 * reporting to the user.
 */
public class AdjacencyListTest {
    /**
     * Helper method: Counts the number of elements in an iterator
     */
    private static <T> int countIterator(IIterator<T> it) {
        int count = 0;
        while (it.hasNext()) {
            it.next();
            count++;
        }
        return count;
    }

    public static void main(String[] args) throws Exception {
        // Create a new adjacency list graph
        IGraph<String, Integer> g = new AdjacencyListGraph<String, Integer>();

        System.out.println("===== Testing Adjacency List Graph Implementation =====");

        // Create vertices
        System.out.println("Creating vertices...");
        IVertex<String> hnl = g.insertVertex("HNL");
        IVertex<String> lax = g.insertVertex("LAX");
        IVertex<String> sfo = g.insertVertex("SFO");
        IVertex<String> ord = g.insertVertex("ORD");
        IVertex<String> dfw = g.insertVertex("DFW");
        IVertex<String> lga = g.insertVertex("LGA");
        IVertex<String> pvd = g.insertVertex("PVD");
        IVertex<String> mia = g.insertVertex("MIA");

        // Create edges
        System.out.println("Creating edges...");
        IEdge<Integer> hnllax = g.insertEdge(hnl, lax, 2555);
        IEdge<Integer> laxsfo = g.insertEdge(lax, sfo, 337);
        IEdge<Integer> ordsfo = g.insertEdge(ord, sfo, 1843);
        IEdge<Integer> laxord = g.insertEdge(lax, ord, 1743);
        IEdge<Integer> dfwlax = g.insertEdge(dfw, lax, 1233);
        IEdge<Integer> ordpvd = g.insertEdge(ord, pvd, 849);
        IEdge<Integer> dfwlga = g.insertEdge(dfw, lga, 1387);
        IEdge<Integer> dfwmia = g.insertEdge(dfw, mia, 1120);
        IEdge<Integer> lgamia = g.insertEdge(lga, mia, 1099);
        IEdge<Integer> lgapvd = g.insertEdge(lga, pvd, 142);

        // Test areAdjacent
        System.out.println("\nTesting areAdjacent method...");
        if (g.areAdjacent(sfo, ord))
            System.out.println("SFO and ORD are adjacent: correct");
        else
            System.out.println("SFO and ORD are adjacent: incorrect");

        if (g.areAdjacent(hnl, ord))
            System.out.println("HNL and ORD are adjacent: incorrect");
        else
            System.out.println("HNL and ORD are adjacent: correct");

        // Test endVertices
        System.out.println("\nTesting endVertices method...");
        IVertex<String>[] ends = g.endVertices(laxord);
        if ((ends[0] == lax && ends[1] == ord) || (ends[1] == lax && ends[0] == ord))
            System.out.println("End vertices of LAX<->ORD: correct");
        else
            System.out.println("End vertices of LAX<->ORD: incorrect");

        // Test opposite
        System.out.println("\nTesting opposite method...");
        if (g.opposite(pvd, lgapvd) == lga)
            System.out.println("Opposite of PVD along LGA<->PVD is LGA: correct");
        else
            System.out.println("Opposite of PVD along LGA<->PVD is LGA: incorrect");

        // Test replace method
        System.out.println("\nTesting replace method...");
        String oldElement = g.replace(mia, "MIA_NEW");
        System.out.println("Old element of MIA before replacement: " + oldElement);
        System.out.println("Element after replacement: " + mia.element());

        int oldDistance = g.replace(dfwlax, 9999);
        System.out.println("Old distance of DFW-LAX edge before replacement: " + oldDistance);
        System.out.println("Distance after replacement: " + dfwlax.element());

        // Test incidentEdges
        System.out.println("\nTesting incidentEdges method...");
        System.out.println("Edges connected to LAX:");
        IIterator<IEdge<Integer>> it = g.incidentEdges(lax);
        int laxEdgeCount = 0;
        while (it.hasNext()) {
            IEdge<Integer> edge = it.next();
            IVertex<String>[] endpoints = g.endVertices(edge);
            System.out.println("  " + endpoints[0].element() + " <-> " +
                    endpoints[1].element() + " (distance: " + edge.element() + ")");
            laxEdgeCount++;
        }
        System.out.println("Number of edges connected to LAX: " + laxEdgeCount);

        // Test removeEdge
        System.out.println("\nTesting removeEdge method...");
        int removedDistance = g.removeEdge(lgapvd);
        System.out.println("Removed LGA<->PVD edge, distance: " + removedDistance);

        // Verify the edge has been removed
        if (g.areAdjacent(lga, pvd))
            System.out.println("LGA and PVD are still adjacent: incorrect");
        else
            System.out.println("LGA and PVD are no longer adjacent: correct");

        // Test removeVertex
        System.out.println("\nTesting removeVertex method...");
        String removedVertex = g.removeVertex(hnl);
        System.out.println("Removed vertex " + removedVertex);

        // Check if the number of edges connected to LAX has decreased
        it = g.incidentEdges(lax);
        int newLaxEdgeCount = 0;
        while (it.hasNext()) {
            it.next();
            newLaxEdgeCount++;
        }
        System.out.println("Number of edges connected to LAX after removing HNL: " + newLaxEdgeCount);
        if (newLaxEdgeCount < laxEdgeCount)
            System.out.println("Number of edges connected to LAX has decreased: correct");
        else
            System.out.println("Number of edges connected to LAX has not decreased: incorrect");

        // Test exception handling
        System.out.println("\nTesting exception handling...");
        try {
            g.insertEdge(lax, lax, 999);
            System.out.println("Self-loop edges allowed: incorrect");
        } catch (Exception e) {
            System.out.println("Self-loop edges correctly rejected: correct");
        }

        try {
            IVertex<String> newVertex = g.insertVertex(null);
            System.out.println("Null element vertices allowed: incorrect");
        } catch (IllegalArgumentException e) {
            System.out.println("Null element vertices correctly rejected: correct");
        }

        // Test empty graph
        System.out.println("\nTesting empty graph operations...");
        IGraph<String, Integer> emptyGraph = new AdjacencyListGraph<String, Integer>();
        System.out.println("Empty graph should have 0 vertices: "
                + (countIterator(emptyGraph.vertices()) == 0 ? "correct" : "incorrect"));
        System.out.println("Empty graph should have 0 edges: "
                + (countIterator(emptyGraph.edges()) == 0 ? "correct" : "incorrect"));

        // Print all remaining vertices
        System.out.println("\nAll remaining vertices:");
        IIterator<IVertex<String>> vit = g.vertices();
        while (vit.hasNext()) {
            IVertex<String> v = vit.next();
            System.out.println("  " + v.element());
        }

        // Print all edges
        System.out.println("\nAll remaining edges:");
        IIterator<IEdge<Integer>> eit = g.edges();
        while (eit.hasNext()) {
            IEdge<Integer> e = eit.next();
            IVertex<String>[] endPoints = g.endVertices(e);
            System.out.println("  " + endPoints[0].element() + " <-> " +
                    endPoints[1].element() + " (distance: " + e.element() + ")");
        }
    }
}